package com.allesblinkt.mucom.server;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import java.util.logging.Level;
import java.util.logging.Logger;

import org.simpleframework.http.Form;
import org.simpleframework.http.Part;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import org.simpleframework.http.core.Container;

import com.allesblinkt.mucom.common.ClientItem;
import com.allesblinkt.mucom.common.ClientRequest;
import com.allesblinkt.mucom.common.Constants;
import com.allesblinkt.mucom.common.ContentItem;
import com.allesblinkt.mucom.common.ServerResponse;
import com.allesblinkt.mucom.common.Utils;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * @author mys
 *
 */
public class ServerController implements Constants, Container {

	private Logger logger;

	private XStream xstream;
	
	private int fileCount = 0;

	/**
	 * Store list of known active clients. Can be retrieved by their unique hash
	 */
	private Hashtable<String, ClientItem> clients;
	

	/**
	 * Used to store and keep the order of clients
	 */
	private Vector<ClientItem> clientOrder;


	/**
	 * Client active at the moment
	 */
	private ClientItem activeClient;

	/**
	 * Previously active client
	 */
	private ClientItem lastActiveClient;

	public ServerController() {
		logger = Logger.getLogger(SERVER_LOGGER);

		xstream = new XStream(new DomDriver());
		xstream.alias("request", ClientRequest.class);

		xstream.autodetectAnnotations(true);

		
		clients = new Hashtable<String, ClientItem>();
		clientOrder = new Vector<ClientItem>();
	}

	public void handle(Request theRequest, Response theResponse) {

		logger.log(Level.INFO, theRequest.getTarget());



		try {


			long time = System.currentTimeMillis();

			theResponse.set("Server", SERVER_STRING);
			theResponse.setDate("Date", time);
			theResponse.setDate("Last-Modified", time);


			if(theRequest.getMethod().equals("GET")){
				handleFileRequest(theRequest, theResponse);
			} else if (theRequest.getMethod().equals("POST")){


				System.out.println(theRequest.getContentLength());



				/* Get the POST content and see if it is an RequestObject */

				Form form = theRequest.getForm();

				Object requestObject = null;
				if(theRequest.getContentLength() != -1 && form.size() == 0){
					requestObject = xstream.fromXML(theRequest.getInputStream());
				} else if(form.size() > 0){
					for(Part part :form.getParts()){
						if(part.getName().equals("request")) {
							requestObject = xstream.fromXML(part.getContent());
						}
					}
				} else {
					handle404(theRequest, theResponse);
				}




				if(requestObject instanceof ClientRequest){
					ClientRequest clientRequest = (ClientRequest)requestObject;

					handleClientRequest(clientRequest, theRequest, theResponse);


				} else {
					handle404(theRequest, theResponse);			
				}

			} else {
				handle404(theRequest, theResponse);
			}



			theResponse.close();






		}

		catch (Exception e) {
			logger.log(Level.WARNING, "Server failed to answer  \nReason: "
					+ e.toString());

			e.printStackTrace();

		}

	}
	


	private void handleFileRequest(Request theRequest, Response theResponse) throws Exception {

		
		String[] pathSegments = theRequest.getAddress().getPath().getSegments();
		
		
		File file = null;
		
		int hashId = Integer.MIN_VALUE; 
		
		
		try {
			hashId = Integer.valueOf(pathSegments[0]);
		} catch (Exception e) {
			hashId = Integer.MIN_VALUE; 
		}
		
		for(ClientItem client :clientOrder){
			for(ContentItem contentItem:client.getContentItems()){
				if(contentItem.getHashId() == hashId) file = contentItem.getFile();
			}
		}
		
	
		if(file != null){
			
			
			InputStream fileStream = new FileInputStream(file);
			byte[] chunk = new byte[(int)file.length()];
			int count = 0;
			int pos = 0;

			while((count = fileStream.read(chunk, pos, chunk.length - pos)) > 0) {

				pos += count;
			}


			theResponse.set("Content-Type", "application/octet-stream");

			OutputStream out = theResponse.getOutputStream();


			out.write(chunk);
		} else {
			handle404(theRequest, theResponse);
		}
		
	
	}
	

	private  void handleClientRequest(ClientRequest theClientRequest, Request theRequest, Response theResponse) throws Exception{
		theResponse.set("Content-Type", CONTENT_TYPE);


		ClientItem client = theClientRequest.getClient();

		/* Add client to list of known clients */
		if (!clients.containsKey(client.getHash())) {
			clients.put(client.getHash(), client);

			logger.log(Level.INFO, "New client \"" + client.getHostname()
					+ "\" found.");

			clientOrder.add(client);
		}

		/* Retrieve client by hash out of the list of know clients */
		ClientItem oldClient = clients.get(client.getHash());
		oldClient.setLastSeen(System.currentTimeMillis());

		/* The client is finished, or there is no active one. */
		if ((theClientRequest.getType() == ClientRequest.RequestType.HANDOVER && oldClient
				.equals(activeClient))
				|| activeClient == null ) {

			lastActiveClient = activeClient;

			activeClient = findNextClient();
			
			activeClient.setActiveSince(System.currentTimeMillis());


		}
		
		/* The client has taken way to long */
		if ( oldClient.equals(activeClient) && System.currentTimeMillis() - oldClient.getActiveSince() > SCREENTIME_KILL * 1000 ) {

			lastActiveClient = activeClient;

			activeClient = findNextClient();
			
			activeClient.setActiveSince(System.currentTimeMillis());

		}


		/* Receive a file */
		if(theClientRequest.getType() == ClientRequest.RequestType.UPLOAD){
			logger.log(Level.INFO, "Upload request from client \"" + client.getHostname()
					+ "\" .");

			Part uploadPart  = theRequest.getForm().getPart("upload");

			if(uploadPart != null){
				File temp = File.createTempFile("mucom", ".tmp");
				temp.deleteOnExit();

				FileOutputStream fileOutput = new FileOutputStream(temp);

				InputStream inputStream = uploadPart.getInputStream();


				byte buf[]=new byte[1024];
				int len;
				while((len=inputStream.read(buf))>0){
					fileOutput.write(buf,0,len);

				}
				fileOutput.close();
				inputStream.close();
				
				logger.log(Level.INFO, "File written: "+temp.getAbsolutePath());
				
				
				ContentItem newContentItem = new ContentItem();
				newContentItem.setFile(temp);
				newContentItem.setHashId(fileCount);
				newContentItem.setLocalName(Utils.sanitizeFileName(uploadPart.getFileName()));
				
				fileCount++;
				
				
				if(oldClient.getContentItems().size() > MAX_CLIENT_FILES-1){
					
					oldClient.getContentItems().remove(0);
				}
				
				oldClient.getContentItems().add(newContentItem);
			}



		}

	
		
		
		/* Remove files */
		if(theClientRequest.getTakenItems() != null){
			if(theClientRequest.getTakenItems().size() > 0){
				
				Vector<ContentItem> purgeItems = new Vector<ContentItem>();
				for(ContentItem deadContentItem: theClientRequest.getTakenItems()){
					
					
					for(ClientItem purgeClient: clientOrder){
						for(ContentItem storedContentItem:purgeClient.getContentItems()){
							
							
							
							if(storedContentItem.getHashId() == deadContentItem.getHashId()){
								purgeItems.add(storedContentItem);
			

							}
							
						}
						
						purgeClient.getContentItems().removeAll(purgeItems);

					}
					
					
					
					
					
				}
				
			}
		}
		



		PrintStream body = theResponse.getPrintStream();


		ServerResponse response = new ServerResponse();

		/* Let the client know if it is active or just idling */
		if (oldClient.equals(activeClient)) {
			response.setType(ServerResponse.ResponseType.ACTIVE);
			
			response.setClients(clientOrder);
		} else {
			response.setType(ServerResponse.ResponseType.IDLE);
		}

		body.println(xstream.toXML(response));

		body.close();


	}


	private void handle404( Request theRequest, Response theResponse) throws Exception{
		theResponse.setCode(404);
		theResponse.set("Content-Type", "text/plain");

		PrintStream body = theResponse.getPrintStream();

		body.println("404 - not found.\n There is nothing to see for an ordinary browser here.");


		body.close();
	}




	private synchronized ClientItem findNextClient() {

		int lastIndex = -1;

		for (int i = 0; i < clientOrder.size(); i++) {

			ClientItem client = clientOrder.get(i);
			if (client.equals(lastActiveClient))
				lastIndex = i;

		}

		ClientItem nextClient = null;

		if (lastIndex < clientOrder.size() - 1) {
			nextClient = clientOrder.get(lastIndex + 1);
		} else {
			nextClient = clientOrder.get(0);
		}

		return nextClient;
	}

	public Hashtable<String, ClientItem> clients() {
		return clients;
	}

	/**
	 * @return the activeClient
	 */
	public ClientItem getActiveClient() {
		return activeClient;
	}

	/**
	 * @param activeClient
	 *            the activeClient to set
	 */
	public void setActiveClient(ClientItem activeClient) {
		this.activeClient = activeClient;
	}

	/**
	 * @return the clientOrder
	 */
	public Vector<ClientItem> clientOrder() {
		return clientOrder;
	}

	/**
	 * @return the lastActiveClient
	 */
	public ClientItem getLastActiveClient() {
		return lastActiveClient;
	}

	/**
	 * @param lastActiveClient
	 *            the lastActiveClient to set
	 */
	public void setLastActiveClient(ClientItem lastActiveClient) {
		this.lastActiveClient = lastActiveClient;
	}

}
